﻿using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using Abp.Domain.Repositories;
using Abp.Linq.Extensions;
using Abp.Runtime.Validation;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace NetCore.LinqExtensions
{
    public static class QueryableExtensions
    {
        /// <summary>
        /// 过滤日期的实体范围
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="query"></param>
        /// <param name="predicate">日期字段</param>
        /// <param name="fromDate">开始日期</param>
        /// <param name="toDate">结束日期</param>
        /// <returns></returns>
        public static IQueryable<TEntity> WhereDateRange<TEntity>(this IQueryable<TEntity> query,
            Expression<Func<TEntity, DateTime?>> predicate, DateTime? fromDate,
            DateTime? toDate) where TEntity : IHasCreationTime
        {
            if (fromDate > toDate)
            {
                throw new AbpValidationException("开始日期必须小于或等于结束日期");
            }
            
            //Expression<Func<TEntity, bool>> beginExpression = entity =>
            //    predicate.Compile().Invoke(entity).HasValue &&
            //    predicate.Compile().Invoke(entity).Value.Date >= fromDate.Value;
            //Expression<Func<TEntity, bool>> endExpression = entity =>
            //    predicate.Compile().Invoke(entity).HasValue &&
            //    predicate.Compile().Invoke(entity).Value.Date < toDate.Value.Date.AddDays(1);
            //query.WhereIf(fromDate.HasValue, beginExpression)
            //    .WhereIf(toDate.HasValue, endExpression);

            return query.WhereIf(fromDate.HasValue, m => m.CreationTime >= fromDate)
                .WhereIf(toDate.HasValue, m => m.CreationTime < fromDate);

        }

        ///// <summary>
        ///// 过滤创建日期的实体范围 改进：日期验证需要考虑开闭区间在dto的validate或在业务上设置
        ///// </summary>
        ///// <typeparam name="TEntity"></typeparam>
        ///// <param name="query"></param>
        ///// <param name="fromDate">开始日期</param>
        ///// <param name="toDate">结束日期</param>
        ///// <returns></returns>
        //public static IQueryable<TEntity> WhereCreatedDateRange<TEntity>(this IQueryable<TEntity> query, DateTime? fromDate,
        //    DateTime? toDate) where TEntity : IHasCreationTime
        //{
        //    if (fromDate != null && toDate != null && fromDate.Value > toDate.Value)
        //    {
        //        throw new AbpValidationException("开始日期必须小于或等于结束日期");
        //    }

        //    return query.WhereIf(fromDate.HasValue, x => x.CreationTime.Date >= fromDate.Value)
        //        .WhereIf(toDate.HasValue, x => x.CreationTime.Date < toDate.Value.Date.AddDays(1));
        //}

        /// <summary>
        /// 过滤修改日期的实体范围
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="query"></param>
        /// <param name="fromDate">开始日期</param>
        /// <param name="toDate">结束日期</param>
        /// <returns></returns>
        public static IQueryable<TEntity> WhereModifiedDateRange<TEntity>(this IQueryable<TEntity> query,
            DateTime? fromDate,
            DateTime? toDate) where TEntity : IHasModificationTime
        {
            if (fromDate != null && toDate != null && fromDate.Value > toDate.Value)
            {
                throw new AbpValidationException("开始日期必须小于或等于结束日期");
            }

            return query
                .WhereIf(fromDate.HasValue,
                    x => x.LastModificationTime.HasValue && x.LastModificationTime.Value.Date >= fromDate.Value)
                .WhereIf(toDate.HasValue,
                    x => x.LastModificationTime.HasValue &&
                         x.LastModificationTime.Value.Date < toDate.Value.Date.AddDays(1));
        }

        public static Task<TEntity> GetAsync<TEntity, TPrimaryKey>(this IQueryable<TEntity> query,
            TPrimaryKey id)
            where TEntity : class, IEntity<TPrimaryKey>
        {
            return query.Where(CreateEqualityExpressionForId<TEntity, TPrimaryKey>(id)).FirstAsync();
        }

        public static Task<TDestination> GetWithProjectToAsync<TEntity, TPrimaryKey, TDestination>(
            this IRepository<TEntity, TPrimaryKey> repository,
            TPrimaryKey id)
            where TEntity : class, IEntity<TPrimaryKey>
        {
            return repository.GetAll().AsNoTracking().Where(CreateEqualityExpressionForId<TEntity, TPrimaryKey>(id))
                .ProjectTo<TDestination>()
                .FirstOrDefaultAsync();
        }

        public static Expression<Func<TEntity, bool>> CreateEqualityExpressionForId<TEntity, TPrimaryKey>(TPrimaryKey id) where TEntity : class, IEntity<TPrimaryKey>
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));

            var leftExpression = Expression.PropertyOrField(lambdaParam, "Id");

            Expression<Func<object>> closure = () => id;
            var rightExpression = Expression.Convert(closure.Body, leftExpression.Type);

            var lambdaBody = Expression.Equal(leftExpression, rightExpression);

            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }
    }
}
